/**
 * Created by gaimeng on 14/12/27.
 */

var System = {};
var js = document.scripts;
js = js[js.length - 1].src.substring(0, js[js.length - 1].src.lastIndexOf("/"));
System.path = js;
System.libPath = System.path.substring(0, System.path.lastIndexOf("/"));
System.imgPath = System.libPath + "/img";
System.zoomNumber = 100;
System.config = {
  zoom: 0.08,
  userPanSpeed: 0.03
};
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik M ller. fixes from Paul Irish and Tino Zijdel
// MIT license
(function () {
  var lastTime = 0;
  var vendors = ['ms', 'moz', 'webkit', 'o'];
  for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
    window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] +
    'CancelRequestAnimationFrame'];
  }
  if (!window.requestAnimationFrame) window.requestAnimationFrame = function (callback, element) {
    var currTime = new Date().getTime();
    var timeToCall = Math.max(0, 16 - (currTime - lastTime));
    var id = window.setTimeout(function () {
      callback(currTime + timeToCall);
    }, timeToCall);
    lastTime = currTime + timeToCall;
    return id;
  };
  if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function (id) {
    clearTimeout(id);
  };
}());

//IDM namespace
var IDM = {}
IDM.Browser = {};
//Browser detection
(function () {
  var a = "ActiveXObject" in window,
    c = a && !document.addEventListener,
    e = navigator.userAgent.toLowerCase(),
    f = -1 !== e.indexOf("webkit"),
    m = -1 !== e.indexOf("chrome"),
    p = -1 !== e.indexOf("phantom"),
    isAndroid = -1 !== e.indexOf("android"),
    r = -1 !== e.search("android [23]"),
    gecko = -1 !== e.indexOf("gecko"),
    isIphone = -1 !== e.indexOf("iphone"),
    isSymbianOS = -1 !== e.indexOf("symbianos"),
    isWinPhone = -1 !== e.indexOf("windows phone"),
    isIpad = -1 !== e.indexOf("ipad"),
    k = isIphone || isWinPhone || isSymbianOS || isAndroid || isIpad,
    q = window.navigator && window.navigator.msPointerEnabled && window.navigator.msMaxTouchPoints && !window.PointerEvent,
    t = window.PointerEvent && window.navigator.pointerEnabled && window.navigator.maxTouchPoints || q,
    y = "devicePixelRatio" in window && 1 < window.devicePixelRatio || "matchMedia" in window && window.matchMedia("(min-resolution:144dppi)") &&
      window.matchMedia("(min-resolution:144dppi)").matches,
    l = document.documentElement,
    A = a && "transition" in l.style,
    x = "WebKitCSSMatrix" in window && "m11" in new window.WebKitCSSMatrix && !r,
    B = "MozPerspective" in l.style,
    z = "OTransition" in l.style,
    G = !window.L_DISABLE_3D && (A || x || B || z) && !p,
    p = !window.L_NO_TOUCH && !p && function () {
      if (t || "ontouchstart" in l) return !0;
      var a = document.createElement("div"),
        c = !1;
      if (!a.setAttribute) return !1;
      a.setAttribute("ontouchstart", "return;");
      "function" === typeof a.ontouchstart && (c = !0);
      a.removeAttribute("ontouchstart");
      return c
    }();
  IDM.Browser = {
    ie: a,
    ielt9: c,
    webkit: f,
    gecko: gecko && !f && !window.opera && !a,
    android: isAndroid,
    android23: r,
    iphone: isIphone,
    ipad: isIpad,
    symbian: isSymbianOS,
    winphone: isWinPhone,
    chrome: m,
    ie3d: A,
    webkit3d: x,
    gecko3d: B,
    opera3d: z,
    any3d: G,
    mobile: k,
    mobileWebkit: k && f,
    mobileWebkit3d: k && x,
    mobileOpera: k && window.opera,
    touch: p,
    msPointer: q,
    pointer: t,
    retina: y
  }
}());

//---------------------the IDM.GeomUtil class--------------------
//get the bounding Rect of the points
function Rect(minx, miny, maxx, maxy) {
  this.tl = [minx || 0, miny || 0]; //top left point
  this.br = [maxx || 0, maxy || 0]; //bottom right point
}

Rect.prototype.isCollide = function (rect) {
  if (rect.br[0] < this.tl[0] || rect.tl[0] > this.br[0] ||
    rect.br[1] < this.tl[1] || rect.tl[1] > this.br[1]) {
    return false;
  }
  return true;
}

IDM.GeomUtil = {

  getBoundingRect: function (points) {
    var rect = new Rect();
    //if there are less than 1 point
    if (points.length < 2) {
      return rect;
    }
    var minX = 9999999, minY = 9999999, maxX = -9999999, maxY = -9999999;
    for (var i = 0; i < points.length - 1; i += 2) {

      if (points[i] > maxX) {
        maxX = points[i];
      }
      if (points[i] < minX) {
        minX = points[i];
      }
      if (points[i + 1] > maxY) {
        maxY = points[i + 1];
      }
      if (points[i + 1] < minY) {
        minY = points[i + 1];
      }
    }
    rect.tl = [minX, minY];
    rect.br = [maxX, maxY];
    return rect;
  }
}
//---------------------the IDM.DomUtil class--------------------
IDM.DomUtil = {

  getElementLeft: function (element) {
    var actualLeft = element.offsetLeft;
    var current = element.offsetParent;
    while (current !== null) {
      actualLeft += current.offsetLeft;
      current = current.offsetParent;
    }
    return actualLeft;
  },

  getElementTop: function (element) {

    var actualTop = element.offsetTop;
    var current = element.offsetParent;
    while (current !== null) {
      actualTop += current.offsetTop;
      current = current.offsetParent;
    }
    return actualTop;
  },

  getTranslateString: function (point) {
    var dim = IDM.Browser.webkit3d;
    return "translate" + (dim ? "3d" : "") + "(" + point[0] + "px," + point[1] + "px" + ((dim ? ",0" : "") + ")");
  },

  getPos: function (element) {
    return element._idm_pos ? element._idm_pos : [IDM.DomUtil.getElementLeft(element), IDM.DomUtil.getElementTop(element)];
  },
  setPos: function (element, point) {
    element._idm_pos = point;
    IDM.Browser.any3d ? element.style[IDM.DomUtil.TRANSFORM] = IDM.DomUtil.getTranslateString(point) : (element.style.left = point[0] + "px", element.style.top = point[1] + "px")
    //element.style.left = point[0] + "px";
    //element.style.top = point[1] + "px";
  },

  testProp: function (props) {
    for (var c =
      document.documentElement.style, i = 0; i < props.length; i++)
      if (props[i] in c) return props[i];
    return false;
  }
}

IDM.DomUtil.TRANSFORM = IDM.DomUtil.testProp(["transform", "WebkitTransform", "OTransform", "MozTransform", "msTransform"]);
IDM.DomUtil.TRANSITION = IDM.DomUtil.testProp(["webkitTransition", "transition", "OTransition", "MozTransition", "msTransition"]);
IDM.DomUtil.TRANSITION_END = "webkitTransition" === IDM.DomUtil.TRANSITION || "OTransition" === IDM.DomUtil.TRANSITION ? IDM.DomUtil.TRANSITION + "End" : "transitionend";

//---------------------the Mall class--------------------
function Mall() {
  var _this = this;
  this.floors = [];   //the floors
  this.building = null; //the building
  this.root = null; //the root scene
  this.is3d = true;
  this.jsonData = null; //original json data

  var _curFloorId;

  //get building id
  this.getBuildingId = function () {
    var mallid = _this.jsonData.data.building.Mall;
    return mallid ? mallid : -1;
  }

  //get default floor id
  this.getDefaultFloorId = function () {
    return _this.jsonData.data.building.DefaultFloor;
  }
  //get current floor id
  this.getCurFloorId = function () {
    return _curFloorId;
  }

  //get floor num
  this.getFloorNum = function () {
    return _this.jsonData.data.Floors.length;
  }

  //get floor by id
  this.getFloor = function (id) {
    for (var i = 0; i < _this.floors.length; i++) {
      if (_this.floors[i]._id == id) {
        return _this.floors[i];
      }
    }
    return null;
  }

  //get floor by name
  this.getFloorByName = function (name) {
    for (var i = 0; i < _this.floors.length; i++) {
      if (_this.floors[i].Name == name) {
        return _this.floors[i];
      }
    }
    return null;
  }

  //get current floor
  this.getCurFloor = function () {
    return _this.getFloor(_curFloorId);
  }

  //get Floor's json data
  this.getFloorJson = function (fid) {
    var floorsJson = _this.jsonData.data.Floors;
    for (var i = 0; i < floorsJson.length; i++) {
      if (floorsJson[i]._id == fid) {
        return floorsJson[i];
      }
    }
    return null;
  }

  //show floor by id
  this.showFloor = function (id) {
    if (_this.is3d) {
      //set the building outline to invisible
      _this.root.remove(_this.building);
      //set all the floors to invisible
      for (var i = 0; i < _this.floors.length; i++) {
        if (_this.floors[i]._id == id) {
          //set the specific floor to visible
          _this.floors[i].position.set(0, 0, 0);
          _this.root.add(_this.floors[i]);
        } else {
          _this.root.remove(_this.floors[i]);
        }
      }
    }
    _curFloorId = id;
  }

  //show the whole building
  this.showAllFloors = function () {
    if (!_this.is3d) { //only the 3d map can show all the floors
      return;
    }

    // _this.root.add(_this.building);

    var offset = 1;
    for (var i = 0; i < _this.floors.length; i++) {
        _this.floors[i].position.set(0,0,i*_this.floors[i].height*offset);

        // _this.floors[i].position.set(-50, -100, i * _this.floors[i].height * offset);
      _this.root.add(this.floors[i]);
    }
    this.building.scale.set(1, 1, offset);

    _curFloorId = 0;

    return _this.root;
  }
}

//----------------------------theme--------------------------------------

var default2dTheme = {
  name: "test", //theme's name
  background: "#F2F2F2", //background color

  //building's style
  building: {
    color: "#000000",
    opacity: 0.1,
    transparent: true,
    depthTest: false
  },

  //floor's style
  floor: {
    color: "#E0E0E0",
    opacity: 1,
    transparent: false
  },

  //selected room's style
  selected: "#ffff55",

  //rooms' style
  room: function (type, category) {
    var roomStyle;
    if (!category) {
      switch (type) {

        case 100: //hollow. u needn't change this color. because i will make a hole on the model in the final version.
          return {
            color: "#F2F2F2",
            opacity: 0.8,
            transparent: true
          }
        case 300: //closed area
          return {
            color: "#AAAAAA",
            opacity: 0.7,
            transparent: true
          };
        case 400: //empty shop
          return {
            color: "#D3D3D3",
            opacity: 0.7,
            transparent: true
          };
        default :
          break;
      }
    }

    switch (category) {
      case 101: //food
        roomStyle = {
          color: "#1f77b4",
          opacity: 0.7,
          transparent: true
        };
        break;
      case 102: //retail
        roomStyle = {
          color: "#aec7e8",
          opacity: 0.7,
          transparent: true
        };
        break;
      case 103: //toiletry
        roomStyle = {
          color: "#ffbb78",
          opacity: 0.7,
          transparent: true
        };
        break;
      case 104: //parent-child
        roomStyle = {
          color: "#98df8a",
          opacity: 0.7,
          transparent: true
        };
        break;
      case 105: //life services
        roomStyle = {
          color: "#bcbd22",
          opacity: 0.7,
          transparent: true
        };
        break;
      case 106: //education
        return {
          color: "#2ca02c",
          opacity: 0.7,
          transparent: true
        };
        break;
      case 107: //life style
        roomStyle = {
          color: "#dbdb8d",
          opacity: 0.7,
          transparent: true
        };
        break;
      case 108: //entertainment
        roomStyle = {
          color: "#EE8A31",
          opacity: 0.7,
          transparent: true
        };
        break;
      case 109: //others
        roomStyle = {
          color: "#8c564b",
          opacity: 0.7,
          transparent: true
        };
      default :
        roomStyle = {
          color: "#c49c94",
          opacity: 0.7,
          transparent: true
        };
        break;
    }
    return roomStyle;
  },

  //room wires' style
  strokeStyle: {
    color: "#666666",
    opacity: 0.8,
    transparent: true,
    linewidth: 1
  },

  fontStyle: {
    opacity: 1,
    textAlign: "center",
    textBaseline: "middle",
    color: "#333333",
    fontsize: 13,
    fontface: "'Lantinghei SC', 'Microsoft YaHei', 'Hiragino Sans GB', 'Helvetica Neue', Helvetica, STHeiTi, Arial, sans-serif  "
  },

  pubPointImg: {
    "101": System.imgPath + "/position.png",
    "102": System.imgPath + "/person.png",
    "11001": System.imgPath + "/toilet.png",
    "11002": System.imgPath + "/ATM.png",
    "21001": System.imgPath + "/stair.png",
    "22006": System.imgPath + "/entry.png",
    "21002": System.imgPath + "/escalator.png",
    "21003": System.imgPath + "/lift.png"
  }
}
var default3dTheme = {
  name: "test", //theme's name
  background: "#F2F2F2", //background color

  //building's style
  building: {
    color: "#000000",
    opacity: 0.1,
    transparent: true,
    depthTest: false
  },

  //floor's style
  floor: {
    color: "#E0E0E0",
    opacity: 1,
    transparent: false
  },

  //selected room's style
  selected: "#ffff55",

  //rooms' style
  room: function (type, category) {
    var roomStyle;
    if (!category) {
      switch (type) {

        case 100: //hollow. u needn't change this color. because i will make a hole on the model in the final version.
          return {
            color: "#F2F2F2",
            opacity: 1,
            // transparent: true
          }
        case 300: //closed area
          return {
            color: "#AAAAAA",
            opacity: 0.9,
            // transparent: true
          };
        case 400: //empty shop
          return {
            color: "#D3D3D3",
            opacity: 0.9,
            // transparent: true
          };
        default :
          break;
      }
    }
    category = 101;
    category += Math.floor(Math.random() * 10)
    switch (category) {
      case 101: //food
        roomStyle = {
          color: "#1f77b4",
          opacity: 0.9,
          // transparent: true
        };
        break;
      case 102: //retail
        roomStyle = {
          color: "#aec7e8",
          opacity: 0.9,
          // transparent: true
        };
        break;
      case 103: //toiletry
        roomStyle = {
          color: "#ffbb78",
          opacity: 0.9,
          // transparent: true
        };
        break;
      case 104: //parent-child
        roomStyle = {
          color: "#98df8a",
          opacity: 0.9,
          // transparent: true
        };
        break;
      case 105: //life services
        roomStyle = {
          color: "#bcbd22",
          opacity: 0.9,
          // transparent: true
        };
        break;
      case 106: //education
        return {
          color: "#2ca02c",
          opacity: 0.9,
          // transparent: true
        };
        break;
      case 107: //life style
        roomStyle = {
          color: "#dbdb8d",
          opacity: 0.9,
          // transparent: true
        };
        break;
      case 108: //entertainment
        roomStyle = {
          color: "#EE8A31",
          opacity: 0.9,
          // transparent: true
        };
        break;
      case 109: //others
        roomStyle = {
          color: "#8c564b",
          opacity: 0.9,
          // transparent: true
        };
      default :
        roomStyle = {
          color: "#c49c94",
          opacity: 0.9,
          // transparent: true
        };
        break;
    }
    return roomStyle;
  },

  //room wires' style
  strokeStyle: {
    linecap: 'round',
    color: "#5C4433",
    opacity: 0.5,
    transparent: true,
    linewidth: 1
  },

  fontStyle: {
    color: "#231815",
    fontsize: 30,
    fontface: "Helvetica, MicrosoftYaHei "
  },

  pubPointImg: {
    "101": System.imgPath + "/position.png",
    "102": System.imgPath + "/person.png",
    "11001": System.imgPath + "/toilet.png",
    "11002": System.imgPath + "/ATM.png",
    "21001": System.imgPath + "/stair.png",
    "22006": System.imgPath + "/entry.png",
    "21002": System.imgPath + "/escalator.png",
    "21003": System.imgPath + "/lift.png"
  }
}

//----------------------------the Loader class --------------------------
IndoorMapLoader = function (is3d) {
  THREE.Loader.call(this, is3d);

  this.withCredentials = false;
  this.is3d = is3d;
};

IndoorMapLoader.prototype = Object.create(THREE.Loader.prototype);

IndoorMapLoader.prototype.load = function (url, callback, texturePath) {

  var scope = this;

  this.onLoadStart();
  this.loadAjaxJSON(this, url, callback);

};

IndoorMapLoader.prototype.loadAjaxJSON = function (context, url, callback, callbackProgress) {

  var xhr = new XMLHttpRequest();

  var length = 0;

  xhr.onreadystatechange = function () {

    if (xhr.readyState === xhr.DONE) {

      if (xhr.status === 200 || xhr.status === 0) {

        if (xhr.responseText) {

          var json = JSON.parse(xhr.responseText);

          var result = context.parse(json);
          callback(result);

        } else {

          console.error('IndoorMapLoader: "' + url + '" seems to be unreachable or the file is empty.');

        }

        // in context of more complex asset initialization
        // do not block on single failed file
        // maybe should go even one more level up

        context.onLoadComplete();

      } else {

        console.error('IndoorMapLoader: Couldn\'t load "' + url + '" (' + xhr.status + ')');

      }

    } else if (xhr.readyState === xhr.LOADING) {

      if (callbackProgress) {

        if (length === 0) {

          length = xhr.getResponseHeader('Content-Length');

        }

        callbackProgress({total: length, loaded: xhr.responseText.length});

      }

    } else if (xhr.readyState === xhr.HEADERS_RECEIVED) {

      if (callbackProgress !== undefined) {

        length = xhr.getResponseHeader('Content-Length');

      }

    }

  };

  xhr.open('GET', url, true);
  xhr.withCredentials = this.withCredentials;
  xhr.send(null);

};

IndoorMapLoader.prototype.parse = function (json) {
  return ParseModel(json, this.is3d);
};

function hexToRgba(hex) {
  var arr = [];
  arr.push(parseInt("0x" + hex.slice(1, 3)));
  arr.push(parseInt("0x" + hex.slice(3, 5)));
  arr.push(parseInt("0x" + hex.slice(5, 7)));
  return arr;
}

//-----------------------------the Parser class ---------------------------------------
function ParseModel(json, is3d, theme) {

  var mall = new Mall();
  var $d3g = {};
  d3threeD($d3g);

  function parse() {

    mall.jsonData = json;
    mall.is3d = is3d;

    if (theme == undefined) {
      if (is3d) {
        theme = default3dTheme;
      } else {
        theme = default2dTheme;
      }
    }

    var building, shape, extrudeSettings, geometry, material, mesh, wire, points, material2, materials;
    var scale = 0.1, floorHeight, buildingHeight = 0;

    //floor geometry
    for (var i = 0; i < json.data.Floors.length; i++) {

      var floor = json.data.Floors[i];

      floor.rect = IDM.GeomUtil.getBoundingRect(floor.Outline[0][0]);

      if (is3d) { // for 3d model
        var floorObj = new THREE.Object3D();

        floorHeight = floor.High / scale;
        if (floorHeight == 0.0) { //if it's 0, set to 50.0
          floorHeight = 50.0;
        }
        buildingHeight += floorHeight;
        points = parsePoints(floor.Outline[0][0]);
        shape = new THREE.Shape(points);
        geometry = new THREE.ShapeGeometry(shape);
        var themeJson = theme.floor;
        themeJson.color = floor.Color;
        mesh = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial(themeJson));
        mesh.position.set(0, 0, -1);

        floorObj.height = floorHeight;
        floorObj.add(mesh);
        floorObj.points = [];
        floorObj._id = floor._id;

        mall.floors.push(floorObj);
      } else {//for 2d model
        floor.strokeStyle = theme.strokeStyle.color;
        floor.fillColor = theme.floor.color;
        mall.floors.push(floor);
      }

      //funcArea geometry
      for (var j = 0; j < floor.FuncAreas.length; j++) {

        var funcArea = floor.FuncAreas[j];
        if (funcArea.Outline === undefined) {
          if (is3d) {
            var simpleShape = $d3g.transformSVGPath(funcArea.Outline2.trim()).toShapes(true);
            var shape3d = new THREE.ExtrudeGeometry(simpleShape, {
              steps: 1,
              amount: 1,
              bevelEnabled: false
            });

            var clor = hexToRgba(funcArea.Color);
            var funcTheme = {
              color: funcArea.Color,
              flatShading: true,
              side: THREE.DoubleSide,
              vertexColors: THREE.VertexColors
            };
            var funcTheme2 = {
              color: getColor("dark", funcArea.Color, 50),
              flatShading: true,
              side: THREE.DoubleSide,
              vertexColors: THREE.VertexColors
            };

            material = new THREE.MeshLambertMaterial(funcTheme);
            material2 = new THREE.MeshLambertMaterial(funcTheme2);
            materials = [];
            materials.push(material)
            materials.push(material2)
            mesh = new THREE.Mesh(shape3d, materials);
            mesh.type = "solidroom";
            mesh.id = funcArea._id;
            mesh.name = funcArea.Name;
            floorObj.add(mesh);
            continue;
          } else {
            continue;
          }
        }
        funcArea.rect = IDM.GeomUtil.getBoundingRect(funcArea.Outline[0][0]);

        if (is3d) {
          points = parsePoints(funcArea.Outline[0][0]);
          shape = new THREE.Shape(points);

          var center = funcArea.Center;
          floorObj.points.push({
            name: funcArea.Name,
            type: funcArea.Type,
            position: new THREE.Vector3(center[0] * scale, floorHeight * scale, -center[1] * scale)
          });

          //solid model
          // extrudeSettings = {amount: floorHeight, bevelEnabled: true};
          extrudeSettings = {
            steps: 1,
            amount: 1,
            bevelEnabled: false
          };

          geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);

          var clor = hexToRgba(funcArea.Color);
          var funcTheme = {
            color: funcArea.Color,
            flatShading: true,
            side: THREE.DoubleSide,
            vertexColors: THREE.VertexColors
          };
          var funcTheme2 = {
            color: getColor("dark", funcArea.Color, 30),
            flatShading: true,
            side: THREE.DoubleSide,
            vertexColors: THREE.VertexColors
          };


          material = new THREE.MeshLambertMaterial(funcTheme);
          material2 = new THREE.MeshLambertMaterial(funcTheme2);
          materials = [];
          materials.push(material)
          materials.push(material2)
          mesh = new THREE.Mesh(geometry, materials);

          mesh.type = "solidroom";
          mesh.id = funcArea._id;
          mesh.name = funcArea.Name;
          floorObj.add(mesh);

          // top wireframe
          // geometry= geometry.setFromPoints(shape);
          geometry = shape.createPointsGeometry();
          wire = new THREE.Line(geometry, new THREE.LineBasicMaterial({color: getColor("dark", funcArea.Color, 40)}));
          wire.position.set(0, 0, 1);

          floorObj.add(wire);
        } else {
          funcArea.fillColor = theme.room(parseInt(funcArea.Type), funcArea.Category).color;
          funcArea.strokeColor = theme.strokeStyle.color;
        }
      }

      if (is3d) {
        //pubPoint geometry
        for (var j = 0; j < floor.PubPoint.length; j++) {
          var pubPoint = floor.PubPoint[j];
          var point = parsePoints(pubPoint.Outline[0][0])[0];
          floorObj.points.push({
            name: pubPoint.Name,
            type: pubPoint.Type,
            position: new THREE.Vector3(point.x * scale, floorHeight * scale, -point.y * scale)
          });
        }
      }
    }

    if (is3d) {
      mall.root = new THREE.Object3D(); //if is 3d, create a root object3D

      //building geometry
      building = json.data.building;
      points = parsePoints(building.Outline[0][0]);
      mall.FrontAngle = building.FrontAngle;

      if (points.length > 0) {
        shape = new THREE.Shape(points);
        extrudeSettings = {amount: buildingHeight, bevelEnabled: false};
        geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
        mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial(theme.building));

        mall.building = mesh;
      }

      //scale the mall
      mall.root.scale.set(scale, scale, scale);
      mall.root.rotateOnAxis(new THREE.Vector3(1, 0, 0), -Math.PI / 2);
    }

    return mall;
  };

  function getColor(key, color, num) {
    if (isNaN(num)) {
      alert("传递的加值不是数字");
      return;
    }
    if (!/^#([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i.test(color)) {
      alert("你传递的颜色不符合规则: #RRGGBB");
      return color;
    }
    var r = parseInt("0x" + RegExp.$1, 16);
    var g = parseInt("0x" + RegExp.$2, 16);
    var b = parseInt("0x" + RegExp.$3, 16);
    num = parseInt(num, 10);
    switch (key) {
      case "red"   :
        r += num;
        break;
      case "green" :
        g += num;
        break;
      case "blue"  :
        b += num;
        break;
      case "light" :
        r += num;
        g += num;
        b += num;
        break;
      case "dark"  :
        r -= num;
        g -= num;
        b -= num;
        break;
    }
    //alert("r = "+ r +"\r\ng = "+ g +"\r\nb = "+ b)
    //操作后的数字正确性判断
    if (r > 255) r = 255;
    if (r < 0) r = 0;
    if (g > 255) g = 255;
    if (g < 0) g = 0;
    if (b > 255) b = 255;
    if (b < 0) b = 0;

    //十进制数字转换到十六进制
    r = r.toString(16);
    g = g.toString(16);
    b = b.toString(16);

    //位数不够两位的补零
    r = ("00" + r).substr(r.length);
    g = ("00" + g).substr(g.length);
    b = ("00" + b).substr(b.length);
    return "#" + r + g + b;
  }

  //parse the points to THREE.Vector2 (remove duplicated points)
  function parsePoints(pointArray) {
    var shapePoints = [];
    for (var i = 0; i < pointArray.length; i += 2) {
      var point = new THREE.Vector2(pointArray[i], pointArray[i + 1]);
      if (i > 0) {
        var lastpoint = shapePoints[shapePoints.length - 1];
        if (point.x != lastpoint.x || point.y != lastpoint.y) { //there are some duplicate points in the original data
          shapePoints.push(point);
        }
      } else {
        shapePoints.push(point);
      }
    }
    return shapePoints;
  }

  return parse();
}


function d3threeD(exports) {

  const DEGS_TO_RADS = Math.PI / 180, UNIT_SIZE = 100;

  const DIGIT_0 = 48, DIGIT_9 = 57, COMMA = 44, SPACE = 32, PERIOD = 46, MINUS = 45;

  exports.transformSVGPath =
    function transformSVGPath(pathStr) {
      var path = new THREE.ShapePath();

      var idx = 1, len = pathStr.length, activeCmd,
        x = 0, y = 0, nx = 0, ny = 0, firstX = null, firstY = null,
        x1 = 0, x2 = 0, y1 = 0, y2 = 0,
        rx = 0, ry = 0, xar = 0, laf = 0, sf = 0, cx, cy;

      function eatNum() {
        var sidx, c, isFloat = false, s;
        // eat delims
        while (idx < len) {
          c = pathStr.charCodeAt(idx);
          if (c !== COMMA && c !== SPACE)
            break;
          idx++;
        }
        if (c === MINUS)
          sidx = idx++;
        else
          sidx = idx;
        // eat number
        while (idx < len) {
          c = pathStr.charCodeAt(idx);
          if (DIGIT_0 <= c && c <= DIGIT_9) {
            idx++;
            continue;
          }
          else if (c === PERIOD) {
            idx++;
            isFloat = true;
            continue;
          }

          s = pathStr.substring(sidx, idx);
          return isFloat ? parseFloat(s) : parseInt(s);
        }

        s = pathStr.substring(sidx);
        return isFloat ? parseFloat(s) : parseInt(s);
      }

      function nextIsNum() {
        var c;
        // do permanently eat any delims...
        while (idx < len) {
          c = pathStr.charCodeAt(idx);
          if (c !== COMMA && c !== SPACE)
            break;
          idx++;
        }
        c = pathStr.charCodeAt(idx);
        return (c === MINUS || (DIGIT_0 <= c && c <= DIGIT_9));
      }

      var canRepeat;
      activeCmd = pathStr[0];
      while (idx <= len) {
        canRepeat = true;
        switch (activeCmd) {
          // moveto commands, become lineto's if repeated
          case 'M':
            x = eatNum();
            y = eatNum();
            path.moveTo(x, y);
            activeCmd = 'L';
            firstX = x;
            firstY = y;
            break;
          case 'm':
            x += eatNum();
            y += eatNum();
            path.moveTo(x, y);
            activeCmd = 'l';
            firstX = x;
            firstY = y;
            break;
          case 'Z':
          case 'z':
            canRepeat = false;
            if (x !== firstX || y !== firstY)
              path.lineTo(firstX, firstY);
            break;
          // - lines!
          case 'L':
          case 'H':
          case 'V':
            nx = (activeCmd === 'V') ? x : eatNum();
            ny = (activeCmd === 'H') ? y : eatNum();
            path.lineTo(nx, ny);
            x = nx;
            y = ny;
            break;
          case 'l':
          case 'h':
          case 'v':
            nx = (activeCmd === 'v') ? x : (x + eatNum());
            ny = (activeCmd === 'h') ? y : (y + eatNum());
            path.lineTo(nx, ny);
            x = nx;
            y = ny;
            break;
          // - cubic bezier
          case 'C':
            x1 = eatNum();
            y1 = eatNum();
          case 'S':
            if (activeCmd === 'S') {
              x1 = 2 * x - x2;
              y1 = 2 * y - y2;
            }
            x2 = eatNum();
            y2 = eatNum();
            nx = eatNum();
            ny = eatNum();
            path.bezierCurveTo(x1, y1, x2, y2, nx, ny);
            x = nx;
            y = ny;
            break;
          case 'c':
            x1 = x + eatNum();
            y1 = y + eatNum();
          case 's':
            if (activeCmd === 's') {
              x1 = 2 * x - x2;
              y1 = 2 * y - y2;
            }
            x2 = x + eatNum();
            y2 = y + eatNum();
            nx = x + eatNum();
            ny = y + eatNum();
            path.bezierCurveTo(x1, y1, x2, y2, nx, ny);
            x = nx;
            y = ny;
            break;
          // - quadratic bezier
          case 'Q':
            x1 = eatNum();
            y1 = eatNum();
          case 'T':
            if (activeCmd === 'T') {
              x1 = 2 * x - x1;
              y1 = 2 * y - y1;
            }
            nx = eatNum();
            ny = eatNum();
            path.quadraticCurveTo(x1, y1, nx, ny);
            x = nx;
            y = ny;
            break;
          case 'q':
            x1 = x + eatNum();
            y1 = y + eatNum();
          case 't':
            if (activeCmd === 't') {
              x1 = 2 * x - x1;
              y1 = 2 * y - y1;
            }
            nx = x + eatNum();
            ny = y + eatNum();
            path.quadraticCurveTo(x1, y1, nx, ny);
            x = nx;
            y = ny;
            break;
          // - elliptical arc
          case 'A':
            rx = eatNum();
            ry = eatNum();
            xar = eatNum() * DEGS_TO_RADS;
            laf = eatNum();
            sf = eatNum();
            nx = eatNum();
            ny = eatNum();
            if (rx !== ry) {
              console.warn("Forcing elliptical arc to be a circular one :(",
                rx, ry);
            }
            // SVG implementation notes does all the math for us! woo!
            // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
            // step1, using x1 as x1'
            x1 = Math.cos(xar) * (x - nx) / 2 + Math.sin(xar) * (y - ny) / 2;
            y1 = -Math.sin(xar) * (x - nx) / 2 + Math.cos(xar) * (y - ny) / 2;
            // step 2, using x2 as cx'
            var norm = Math.sqrt(
              (rx * rx * ry * ry - rx * rx * y1 * y1 - ry * ry * x1 * x1) /
              (rx * rx * y1 * y1 + ry * ry * x1 * x1));
            if (laf === sf)
              norm = -norm;
            x2 = norm * rx * y1 / ry;
            y2 = norm * -ry * x1 / rx;
            // step 3
            cx = Math.cos(xar) * x2 - Math.sin(xar) * y2 + (x + nx) / 2;
            cy = Math.sin(xar) * x2 + Math.cos(xar) * y2 + (y + ny) / 2;

            var u = new THREE.Vector2(1, 0),
              v = new THREE.Vector2((x1 - x2) / rx,
                (y1 - y2) / ry);
            var startAng = Math.acos(u.dot(v) / u.length() / v.length());
            if (u.x * v.y - u.y * v.x < 0)
              startAng = -startAng;

            // we can reuse 'v' from start angle as our 'u' for delta angle
            u.x = (-x1 - x2) / rx;
            u.y = (-y1 - y2) / ry;

            var deltaAng = Math.acos(v.dot(u) / v.length() / u.length());
            // This normalization ends up making our curves fail to triangulate...
            if (v.x * u.y - v.y * u.x < 0)
              deltaAng = -deltaAng;
            if (!sf && deltaAng > 0)
              deltaAng -= Math.PI * 2;
            if (sf && deltaAng < 0)
              deltaAng += Math.PI * 2;

            path.absarc(cx, cy, rx, startAng, startAng + deltaAng, sf);
            x = nx;
            y = ny;
            break;
          default:

            throw new Error("weird path command: |" + activeCmd + "|");
        }
        // just reissue the command
        if (canRepeat && nextIsNum())
          continue;
        activeCmd = pathStr[idx++];
      }

      return path;
    }

}


//-----------------------------the IndoorMap class ------------------------------------

var IndoorMap = function (params) {
  var _this = this;
  var _mapDiv, _uiRoot, _uiSelected;
  var _fullScreen = false;
  this.is3d = true;
  var _indoorMap;

  //initialization
  function init(params) {

    //parse the parameters
    if (params != undefined) {
      if (params.hasOwnProperty("zoom")) {
        System.config.zoom = params.zoom;
      }
      if (params.hasOwnProperty("userPanSpeed")) {
        System.config.userPanSpeed = params.userPanSpeed;
      }
      //if the map container is specified
      if (params.hasOwnProperty("mapDiv")) {
        _mapDiv = document.getElementById(params.mapDiv);
        _fullScreen = true;
      }
      //if the map size is specified
      else if (params.hasOwnProperty("size") && params.size.length == 2) {
        createMapDiv(params.size, params.parent);
        _fullScreen = true;
      }
      //else create a full screen map
      else {
        createMapDiv([window.innerWidth, window.innerHeight]);
        _fullScreen = true;
      }
      // 2d or 3d view
      if (params.hasOwnProperty("dim")) {
        _this.is3d = params.dim == "2d" ? false : true;
      } else {
        _this.is3d = true;
      }
    } else {
      createMapDiv([window.innerWidth, window.innerHeight]);
      _fullScreen = true;
    }

    // create 2d or 3d map by webgl detection
    if (_this.is3d && Detector.webgl) {
      _indoorMap = new IndoorMap3d(_mapDiv);
    } else {
      _indoorMap = new IndoorMap2d(_mapDiv);
      _this.is3d = false;
    }

    //var marker = document.createElement("image");
    //marker.style.position = "absolute";
    //marker.style.src = System.imgPath+"/marker.png";
    //marker.visibility = false;
    //marker.style.width = "39px";
    //marker..height = "54px";
    //document.body.appendChild(marker);
    ////_indoorMap.setSelectionMarker(marker);

    var hammertime = new Hammer.Manager(document.getElementById('indoor3d'));

    // hammertime.get("rotate").set({enable: true});
    // hammertime.get('pinch').set({enable: true});
    hammertime.add(new Hammer.Pinch({threshold: 0.06}));
    hammertime.add(new Hammer.Rotate({threshold: 10})).recognizeWith(hammertime.get('pinch'));


    hammertime.on("rotatestart rotatemove rotateend", onRotate);

    function onRotate(ev) {
      console.log(ev)
      //点下第二个触控点时触发
      if (ev.type == 'rotatestart') {
        startRotateAngle = ev.rotation;
        tempAngleFlag = 0;
      }
      if (ev.type == 'rotatemove') {
        if (tempAngleFlag == 0) {
          preAngle = startRotateAngle;
          tempAngleFlag++;
        } else {
          deltaAngle = ev.rotation - preAngle;
          if (deltaAngle > 0) {
            _indoorMap.setRotateLeft();
          } else {
            _indoorMap.setRotateRight();
          }
        }

      }
    }

    hammertime.on("pinchout", function (e) {
      if (_this.is3d) {
        _indoorMap.zoomIn();

      }
    });
    hammertime.on("pinchin", function (e) {
      if (_this.is3d) {
        _indoorMap.zoomOut();
      }
    });


    // var mc = new Hammer.Manager(document.getElementById('indoor3d'));
    // mc.add(new Hammer.Rotate({ threshold: 0 }));
    // mc.add(new Hammer.Pinch({ threshold: 0 }));
    // mc.on("rotatestart rotatemove rotateend", onRotate);
    // mc.on("pinchout pinchin", onPinch);
    //
    //
    // function onPinch(ev){
    //   if(ev.type == 'pinchout') {
    //     _indoorMap.zoomIn();
    //   }else {
    //     _indoorMap.zoomOut();
    //   }
    //
    // }
    // //旋转相关
    // var  preAngle =0 ;
    // var  tempAngleFlag=0;
    // var  deltaAngle = 0;
    // var  startRotateAngle = 0;
    //
    //
    // function onRotate(ev) {
    //   console.log(ev)
    //   //点下第二个触控点时触发
    //   if(ev.type == 'rotatestart') {
    //     startRotateAngle =  ev.rotation ;
    //     tempAngleFlag = 0 ;
    //   }
    //   if(ev.type == 'rotatemove'){
    //     if(tempAngleFlag == 0){
    //       preAngle = startRotateAngle;
    //       tempAngleFlag ++;
    //     }else{
    //     if(ev.rotation>0){
    //       _indoorMap.setRotateLeft();
    //     }
    //       _indoorMap.setRotateRight();
    //     }
    //
    //   }
    // }

  }

  function setRotate(tete) {
    _indoorMap.setRotate(tete);
  }

  function setUserPosition(position) {
    _indoorMap.setRotate(position);
  }

  function createMapDiv(size, parent) {
    _mapDiv = document.createElement("div");
    _mapDiv.style.width = size[0] + "px";
    _mapDiv.style.height = size[1] + "px";
    _mapDiv.style.top = "0px";
    _mapDiv.style.left = "0px";
    _mapDiv.style.position = "absolute";
    _mapDiv.id = "indoor3d";

    if (parent) {
      parent.appendChild(_mapDiv);
      parent.style.margin = "0";
    } else {
      document.body.appendChild(_mapDiv);
      document.body.style.margin = "0";
    }
  }


  function updateUI() {
    if (_uiRoot == null) {
      return;
    }
    var ulChildren = _uiRoot.children;
    if (ulChildren.length == 0) {
      return;
    }
    if (_uiSelected != null) {
      _uiSelected.className = "";
    }
    var curid = _this.mall.getCurFloorId();
    if (curid == 0) {
      _uiSelected = _uiRoot.children[0];
    } else {
      for (var i = 0; i < ulChildren.length; i++) {
        if (ulChildren[i].innerText == _this.mall.getCurFloorId().toString()) {
          _uiSelected = ulChildren[i];
        }
      }
    }
    if (_uiSelected != null) {
      _uiSelected.className = "selected";
    }
  }

  init(params);
  return _indoorMap;
}

//get the UI
IndoorMap.getUI = function (indoorMap) {
  var _indoorMap = indoorMap;
  if (_indoorMap == undefined || _indoorMap.mall == null) {
    console.error("the data has not been loaded yet. please call this function in callback")
    return null;
  }
  //create the ul list
  _uiRoot = document.createElement('ul');
  _uiRoot.className = 'floorsUI';

  if (_indoorMap.is3d) {
    var li = document.createElement('li');
    var text = document.createTextNode('All');

    li.appendChild(text);
    _uiRoot.appendChild(li);
    li.onclick = function () {
      _indoorMap.showAllFloors();
    }
  }

  var floors = _indoorMap.mall.jsonData.data.Floors;
  for (var i = 0; i < floors.length; i++) {
    (function (arg) {
      li = document.createElement('li');
      text = document.createTextNode(floors[arg].Name);
      li.appendChild(text);
      li.onclick = function () {
        _indoorMap.showFloor(floors[arg]._id);
      }
      _uiRoot.appendChild(li);
    })(i);
  }
  return _uiRoot;
}
